Análisis de un datatset de Kaggle

En este caso vamos a sacar los datos de un dataset de Kaggle sobre análisis de sentimiento en Twitter.

Los datos

Este análisis está basado en el dataset de Kaggle Tweet Sentiment Extraction.

Vamos a hacer un análisis de sentimiento.

Primero cargamos librería y los datasets, kaggle ya nos proporciona los datos separados en train y test, podríamos unirlos y hacer esta división pero consideramos dejarla tal y como nos lo proporciona kaggle dado que en el dataset train disponemos de una variable adicional.

En el conjunto de train, se proporciona una palabra o frase extraída del tweet (selected_text) que encapsula el sentimiento proporcionado. En el cojunto de test, sólo encontramos text y no selected_text.

Las variables (columnas) de las que disponemos son:

* textID - unique ID for each piece of text
* text - the text of the tweet
* sentiment - the general sentiment of the tweet
* selected_text - [train only] the text that supports the tweet's sentiment

Cargamos las librerías necesarias:

library(cowplot)
library(tm)
library(tidyverse)
library(stringr)
# Para la distancia Jaccard 
library(stringdist)
# Para el análisis de sentimiento
#install.packages('sentimentr')
library(sentimentr)
# other textmining stuff
library(wordcloud)

Cargamos los datos.

train <- read.csv('./data/train.csv')
test <- read.csv('./data/test.csv')
class(train)
## [1] "data.frame"

Los dataset son dataframes.

Vemos las dimensiones.

dim(train)
## [1] 27481     4
dim(test)
## [1] 3534    3

Teniendo en cuenta tanto train como test, tenemos más de 30k datos.

Realicemos un pequeño análisis de los datos.

summary(train) # no hay NAs
##     textID              text           selected_text       sentiment        
##  Length:27481       Length:27481       Length:27481       Length:27481      
##  Class :character   Class :character   Class :character   Class :character  
##  Mode  :character   Mode  :character   Mode  :character   Mode  :character
summary(test)
##     textID              text            sentiment        
##  Length:3534        Length:3534        Length:3534       
##  Class :character   Class :character   Class :character  
##  Mode  :character   Mode  :character   Mode  :character
str(train)
## 'data.frame':    27481 obs. of  4 variables:
##  $ textID       : chr  "cb774db0d1" "549e992a42" "088c60f138" "9642c003ef" ...
##  $ text         : chr  " I`d have responded, if I were going" " Sooo SAD I will miss you here in San Diego!!!" "my boss is bullying me..." " what interview! leave me alone" ...
##  $ selected_text: chr  "I`d have responded, if I were going" "Sooo SAD" "bullying me" "leave me alone" ...
##  $ sentiment    : chr  "neutral" "negative" "negative" "negative" ...
str(test)
## 'data.frame':    3534 obs. of  3 variables:
##  $ textID   : chr  "f87dea47db" "96d74cb729" "eee518ae67" "01082688c6" ...
##  $ text     : chr  "Last session of the day  http://twitpic.com/67ezh" " Shanghai is also really exciting (precisely -- skyscrapers galore). Good tweeps in China:  (SH)  (BJ)." "Recession hit Veronique Branquinho, she has to quit her company, such a shame!" " happy bday!" ...
##  $ sentiment: chr  "neutral" "positive" "negative" "positive" ...

Dado que el dataset de train contiene más de 27k obs. y, además, contiene la variable adicional selected_text, consideramos oportuno realizar el análisis de sentimiento sobre este dataset.

Parece que no hay datos NA’s, pero comprobemos.

any(is.na(train))
## [1] FALSE
any(is.na(test))
## [1] FALSE

El sentimiento (sentiment) es nuestra variable objetivo, nuestra target, vamos a verlo en una tabla.

table(train$sentiment)
## 
## negative  neutral positive 
##     7781    11118     8582
table(test$sentiment)
## 
## negative  neutral positive 
##     1001     1430     1103

Parece predominar el sentimiento neutral, luego el positivo y por el último el negativo.

Visualmente, con las funciones del paquete base de R, hacemos un gráfico.

plot(table(train$sentiment))

Podemos visualizar con otra librería, ggplot2, mucho mas potente, con una visualización más óptima.

library(ggplot2)
library(gridExtra)
## 
## Attaching package: 'gridExtra'
## The following object is masked from 'package:dplyr':
## 
##     combine
pie <- ggplot(train, aes(x = factor(1), fill = factor(sentiment))) +
 geom_bar(width = 1)
pie + coord_polar(theta = "y")

Visualización con barras.

g <- ggplot(train, aes(x = sentiment, fill = factor(sentiment))) +
 geom_bar()
g

Efectivamente, vemos que predomina el sentimiento neutral y, posteriomente, el positivo.

Podríamos decir que el 40% de los tweets son neutral, el 31% positive y el 28% negative, aproximadamente.

Análisis de Sentimiento

Nuestros datasets ya están divididos en train/test, siendo ambos de clase dataframe. El análisis de sentimiento, objetivo de la presente tarea, va a ser realizado sobre los datos de train, como hemos mencionado anteriormente.

Pasamos sentiment a factor.

df <- train
df$sentiment <- as.factor(df$sentiment)

Selección del texto y evaluación de su longitud.

df$text <- as.character(df$text)
df$selected_text <- as.character(df$selected_text)

df$length <- str_length(df$text) # Introduce una nueva variable, lenght (longitud)
df$length_sel <- str_length(df$selected_text) # Introduce una nueva variable, lenght_sel (longitud de selected_text)

Eliminamos las entradas vacías. Los tweets que no tengo longitud mayor a 0 (al menos un caracter escrito).

df <- dplyr::filter(df, length > 0)

Exploramos la longitud del texto frente a la frencuencia. Recordad que el número de caracteres máximo permitidos en Twitter es 280.

summary(df$length)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##    3.00   39.00   64.00   68.35   97.00  159.00
hist(df$length, main='Longitud de Text - Training Set')

boxplot(df$length)

Podríamos decir que los tweets con mayor frecuencia, sobre text, son los que se sitúan en un número de caracteres entre 40-50; es decir, la mediana se situaría entre 40-50. La media se sitúa en 68,35.

Exploramos la longitud del texto seleccionado, selected_text. La variable selected_text, recordemos, encapsula el sentimiento proporcionado.

summary(df$length_sel)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##    1.00    8.00   22.00   36.72   55.00  158.00
hist(df$length_sel, main='Longitud Selected Text - Training Set')

boxplot(df$length_sel)

Sobre selected_text la media se sitúa en 36,72. Y la mediana estaría situada entre 0-10.

Podríamos decir que las palabras requeridas para encapsular el sentimiento del texto no son muchas, es decir, son pocas las palabras requeridas para evaluar el sentimiento (positivo, negativo, neutral) del texto/tweet completo. Realicemos el ratio.

df$length_ratio <- df$length_sel / df$length # Introduce una nueva variable, lenght_ratio
summary(df$length_ratio)
##     Min.  1st Qu.   Median     Mean  3rd Qu.     Max. 
## 0.008929 0.148148 0.632456 0.583509 1.000000 1.000000
hist(df$length_ratio, main='Ratio de Longitud Selected Text vs Full Text')

La media del ratio se sitúa en 0,583509. Y en cuanto a la moda, las palabras que encapsulan el mismo son, con mayor frecuencia, la que albergan todos los caracteres.

Evaluación del comienzo y el final de la subcadena

Búsqueda de la posición inicial y final de selected_text.

Definimos las funciones y variables necesarias.

fun_find_first <- function(text, subtext) {
  foo <- stringr::str_locate(text, fixed(subtext))
  return(foo[1])
}

fun_find_second <- function(text, subtext) {
  foo <- stringr::str_locate(text, fixed(subtext))
  return(foo[2])
}

n <- nrow(df)
as <- 1:n
bs <- 1:n

for (i in 1:n) {
  # print(i)
  text <- df$text[i]
  subtext <- df$selected_text[i]
  a <- fun_find_first(text, subtext)
  b <- fun_find_second(text, subtext)
  as[i] <- a
  bs[i] <- b
}

# Introducimos las variables begin, end, begin_rel y end_rel (relativas)
df$begin <- as
df$end <- bs

df$begin_rel <- df$begin / df$length
df$end_rel <- df$end / df$length

Begin indica en qué caracter comienza selected_text de text. End, en qué caracter finaliza selected_text de text. Begin_rel y end_rel es la diferencia o ratio entre la variables begin y end con lenght.

Visualizaciones.

Inicio relativo.

summary(df$begin_rel)
##     Min.  1st Qu.   Median     Mean  3rd Qu.     Max. 
## 0.006803 0.021277 0.060606 0.234003 0.426229 1.000000
boxplot(df$begin_rel)

Selected_text comienza al inicio de text, en los primeros caracteres generalmente.

hist(df$begin_rel, main='Posición relativa de inicio en selected text')

summary(df$end_rel)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##  0.0229  0.5938  1.0000  0.7955  1.0000  1.0000
boxplot(df$end_rel)

Selected_text finaliza generalmente hacia el final de text, en los últimos caracteres.

hist(df$end_rel, main='Posición relativa de final en selected text')

En realidad, estos gráficos no nos aportan gran información, pero pueden ser de interés. Inicio y final de selected_text muy próximo a incio y final de text, en general.

Distancia Jaccard

El índice de Jaccard o coeficiente de Jaccard mide el grado de similitud entre dos conjuntos, sea cual sea el tipo de elementos.

Siempre toma valores entre 0 y 1, correspondiente este último a la igualdad total entre ambos conjuntos.

# Introduce una nueva variable, jac
df$jac <- stringdist::stringdist(df$text, df$selected_text, method='jaccard')
summary(df$jac)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##  0.0000  0.0000  0.2000  0.3173  0.6667  0.9714

Histograma.

hist(df$jac, 100, main='Distancia Jaccard selected vs full text')

La media se sitúa en 0,3173. Moda en cero (histograma). Los conjuntos no son similares.

Re-evaluación del análsis de sentimiento

Generamos un dataframe, table, llamado sentiment_train con el id por elemento, el conteo de palabras, la desviación típica y ave_sentiment. Sentiment_by usa un algoritmo que aproxima el sentimiento (polaridad) del texto agrupando variables.

Ave_sentiment es el promedio de puntuación de sentimiento/polaridad promedio de agrupación. Es decir, si es negativo, el sentimiento es negativo, si es positivo, indica sentimiento positivo (lo vamos a definir como score_sentiment).

Word_count es el recuento de palabras sumado por variable de agrupación.

Sobre text.

sentiment_train <- sentimentr::sentiment_by(get_sentences(df$text))

# Se introduce en df las variables word_count y sentiment_score
df$sentiment_score <- sentiment_train$ave_sentiment
df$word_count <- sentiment_train$word_count

head(sentiment_train,10)
##     element_id word_count       sd ave_sentiment
##  1:          1          8       NA     0.0000000
##  2:          2         10       NA    -0.4743416
##  3:          3          5       NA    -0.3354102
##  4:          4          5 0.347011    -0.2677943
##  5:          5         14       NA     0.0000000
##  6:          6         15       NA     0.0000000
##  7:          7         14       NA     0.4944333
##  8:          8          2       NA     0.0000000
##  9:          9          3       NA     0.0000000
## 10:         10         10 0.248998     0.2832496

Sobre selected_text, realizamos lo mismo.

sentiment_train_sel <- sentimentr::sentiment_by(get_sentences(df$selected_text))

# Se introduce en df las variables word_count_sel y sentiment_score_sel
df$sentiment_score_sel <- sentiment_train_sel$ave_sentiment
df$word_count_sel <- sentiment_train_sel$word_count

head(sentiment_train_sel,10)
##     element_id word_count        sd ave_sentiment
##  1:          1          8        NA     0.0000000
##  2:          2          2        NA    -0.3535534
##  3:          3          2        NA    -0.5303301
##  4:          4          3        NA    -0.4907477
##  5:          5          2        NA     0.0000000
##  6:          6         15        NA     0.0000000
##  7:          7          1        NA     0.7500000
##  8:          8          2        NA     0.0000000
##  9:          9          3        NA     0.0000000
## 10:         10          5 0.3535534     0.2728432

Correlación entre el SCORE del sentimiento en selected_text y en el texto completo.

sent_cor = round(cor(df$sentiment_score, df$sentiment_score_sel),3)
plot(df$sentiment_score, df$sentiment_score_sel, col='#00000040', pch=16,
     main=paste0('Sentimiento en selected text vs full text; cor=',sent_cor))
grid()

La correlación es perfecta, igual a 1, entre sentiment_score y sentiment_score_sel.

Análisis por cada sentimiento

Realicemos ahora un análisis por cada uno de los sentimientos (positivo, negativo y neutral). Definamos stats_train con 13 variables (medias principalmente, y una mediana).

stats_train <- dplyr::group_by(df, sentiment) %>% summarise(n=n(),
                                                            mean_words = mean(word_count),
                                                            mean_words_sel = mean(word_count_sel),
                                                            mean_length = mean(length),
                                                            mean_length_sel = mean(length_sel),
                                                            mean_ratio = mean(length_ratio),
                                                            median_ratio = median(length_ratio),
                                                            mean_begin_rel = mean(begin_rel),
                                                            mean_end_rel = mean(end_rel),
                                                            mean_jac = mean(jac),
                                                            mean_sentiment_score = mean(sentiment_score),
                                                            mean_sentiment_score_sel = mean(sentiment_score_sel)
)
## `summarise()` ungrouping output (override with `.groups` argument)
stats_train <- as.data.frame(stats_train)

# Resultado
stats_train
##   sentiment     n mean_words mean_words_sel mean_length mean_length_sel
## 1  negative  7781   13.85015       4.042025    70.50932        19.97314
## 2   neutral 11117   12.71044      12.371863    65.23945        62.79329
## 3  positive  8582   13.49091       3.571545    70.43743        18.12713
##   mean_ratio median_ratio mean_begin_rel mean_end_rel   mean_jac
## 1  0.3393714    0.2058824     0.37120726    0.6902020 0.50425156
## 2  0.9634154    1.0000000     0.04857945    0.9874804 0.01922133
## 3  0.3127351    0.1781500     0.34979966    0.6421680 0.53398443
##   mean_sentiment_score mean_sentiment_score_sel
## 1          -0.13112250              -0.33618161
## 2           0.04496499               0.04512524
## 3           0.24948367               0.44112874

Esta tabla es muy interesante, nos da una información muy relevante, bien contenida, bien resumida y muy ilustrativa de nuestro análisis hasta el momento. Un dato que nos parece de relevancia comentar es mean_ratio y median_ratio, en el sentimiento neutral es igual a 1 (mediana) y 0,96 (media); es decir, la longitud de selected_text/longitud de text es en la mayoría de las ocasiones igual a 1 para el setimiento neutral, se usa aquí todo el texto (en este sentimiento), y no pocas palabras de text para definirlo (hecho que sí ocurre para el sentimiento negativa y positivo con mean_ratio de 0,3).

Definimos ahora, separemos más bien por cada uno de los sentimientos.

df_train_neutral <- dplyr::filter(df, sentiment=='neutral')
df_train_positive <- dplyr::filter(df, sentiment=='positive')
df_train_negative <- dplyr::filter(df, sentiment=='negative')

Visualización de las funciones de distribución acumulada de la longitud por sentimientos (ecdf, Empirical Cumulative Distribution Function).

En text.

plot(ecdf(df_train_negative$length), col='red', main='Training - Longitud de text por sentimiento', xlab='Length of Text')
plot(ecdf(df_train_positive$length), col='green', add=TRUE)
plot(ecdf(df_train_neutral$length), col='blue', add=TRUE)
grid()
legend('topleft', text.width=25, legend=c('Negative','Positive','Neutral'), col=c('red','green','blue'), pch=16)

Los tres sentimiento siguen una distribución acumulada muy parecida.

En selected_text.

plot(ecdf(df_train_negative$length_sel), col='red', main='Training - Longitud de selected text por sentimiento', xlab='Length of Selected Text')
plot(ecdf(df_train_positive$length_sel), col='green', add=TRUE)
plot(ecdf(df_train_neutral$length_sel), col='blue', add=TRUE)
grid()
legend('topleft', text.width=25, legend=c('Negative','Positive','Neutral'), col=c('red','green','blue'), pch=16)

Vemos aquí una diferencia en selected_text con respecto a text. Observamos que la distribución acumulada en los sentimiento positivo y negativo tiene mayor pendiente que en el caso del sentimiento neutral.

Visualicemos el ratio selected_text/text.

plot(ecdf(df_train_negative$length_ratio), col='red', main='Training - Ratio Longitud Selected/Text Completo', xlab='Ratio')
plot(ecdf(df_train_positive$length_ratio), col='green', add=TRUE)
plot(ecdf(df_train_neutral$length_ratio), col='blue', add=TRUE)
grid()
legend('topleft', text.width=0.2, legend=c('Negative','Positive','Neutral'), col=c('red','green','blue'), pch=16)

Vemos que en el ratio correspondiente al sentimiento neutral, parece que la logitud acumulada tiene un recorrido muy distinto al del otro par de sentimientos. Anteriormente, había visto que para el sentimiento neutral, mean_ratio era de 0,96 y median_ratio igual a 1.

Histogramas por sentimientos.

En text.

hist(df_train_neutral$sentiment_score, 50, col='blue', main='Sentimiento Score - Sentimientot=Neutral')

hist(df_train_positive$sentiment_score, 50, col='green', main='Sentimiento Score - Sentimiento=Positive')

hist(df_train_negative$sentiment_score, 50, col='red', main='Sentimiento Score - Sentimiento=Negative')

Como cabía esperar, y hemos apuntado anteriormente al indicar el significado de la variable sentiment_score (si se positivo, tendrá valor positivo, si es negativo, negativo y si es neutral se situara entorno al valor 0) la frecuencia en el sentimiento neutral se sitúa entorno al valor 0, la frecuencia en el sentimiento positivo en valores mayores que cero y la frecuencia en el sentimiento negativo, valores menores a 0.

hist(df_train_positive$sentiment_score_sel, 50, col='darkgreen', main='Sentimiento Score Selected Text - Sentimiento=Positive')

hist(df_train_negative$sentiment_score_sel, 50, col='darkred', main='Sentimiento Score Selected Text - Sentimiento=Negative')

En la visualización del histograma sobre selected_text del score por sentimiento, ocurre lo mismo que lo descrito sobre text; sin embargo, observamos picos de frecuencia más pronunciados.

SmoothScatters de la longitud por cada sentimiento, es un scatterplot pero suavizado, una nube de puntos más suavizada.

smoothScatter(df_train_negative$length, df_train_negative$length_sel, xlab='Longitud Text', ylab='Longitud Selected Text',
              main='Training - Sentimiento Negative')

smoothScatter(df_train_positive$length, df_train_positive$length_sel, xlab='Longitud Text', ylab='Longitud Selected Text',
              main='Training - Sentimiento Positive')

smoothScatter(df_train_neutral$length, df_train_neutral$length_sel, xlab='Longitud Text', ylab='Longitud Selected Text',
              main='Training - Sentimiento Neutral')

Estos scatterplots están en sintonía con lo mencionado anteriormente y lo observado en la tabla stats_train, para el sentimiento neutral, selected_text y text son de longitud practicamente coincidente.

Veamos gráficos de correlaciones.

sent_cor = round(cor(df_train_neutral$sentiment_score, df_train_neutral$sentiment_score_sel),3)
plot(df_train_neutral$sentiment_score, df_train_neutral$sentiment_score_sel, col='#0000ff40', pch=16,
     main=paste0('NEUTRAL - Sentimiento selected text vs text completo; cor=',sent_cor))
grid()

La correlación del score para neutral en text y selected_text es de 0,97; positiva y fuerte (lineal).

sent_cor = round(cor(df_train_positive$sentiment_score, df_train_positive$sentiment_score_sel),3)
plot(df_train_positive$sentiment_score, df_train_positive$sentiment_score_sel, col='#00990040', pch=16,
     main=paste0('POSITIVE - Sentimiento selected text vs text completo; cor=',sent_cor))
grid()

La correlación del score para positive en selected_text y text es de 0,527, positiva, muy concentrada entre 0 y 0,5; es decir, es una relación no muy fuerte ya que la nube de puntos tiene una tendencia elíptica o circular, la relación es más bien débil.

sent_cor = round(cor(df_train_negative$sentiment_score, df_train_negative$sentiment_score_sel),3)
plot(df_train_negative$sentiment_score, df_train_negative$sentiment_score_sel, col='#99000040', pch=16,
     main=paste0('NEGATIVE - Sentimiento selected text vs text completo; cor=',sent_cor))
grid()

La correlación del score para negative en selected_text y text es de 0,557, positiva, muy concentrada entre -0.5 y 0; es decir, es una relación no muy fuerte ya que la nube de puntos tiene una tendencia elíptica o circular, la relación es más bien débil.

Histogramas distancia Jaccard por cada sentimiento (negativo, positivo y neutral).

hist(df_train_negative$jac,100, main='NEGATIVE - Jaccard distance selected vs full text', col='red')

hist(df_train_positive$jac,100, main='POSITIVE - Jaccard distance selected vs full text', col='green')

hist(df_train_neutral$jac,100, main='NEUTRAL - Jaccard distance selected vs full text', col='blue')

hist(df_train_neutral$jac[df_train_neutral$jac>0],100, main='NEUTRAL - Jaccard distance selected vs full text - sin ceros', col='blue')

Como hemos mencionado anteriormente, los conjuntos text y selected_text no son similares en cuanto a longitud de caracteres.

Wordclouds por Sentimiento

Sentimiento Negativo:

ETL y construcción del corpus.

my_corpus <- tm::VCorpus(tm::VectorSource(df_train_negative$text))
# tidy text
my_corpus <- tm::tm_map(my_corpus, tm::removePunctuation)
my_corpus <- tm::tm_map(my_corpus, tm::content_transformer(tolower))
my_corpus <- tm::tm_map(my_corpus, removeWords, stopwords("english"))
my_corpus <- tm::tm_map(my_corpus, stemDocument)
# plot wordcloud
wordcloud::wordcloud(my_corpus, max.words=250, random.order=FALSE, color=rainbow(100))

Para el sentimiento negativo pero selecionando sólo selected_text.

my_corpus <- tm::VCorpus(tm::VectorSource(df_train_negative$selected_text))
# tidy text
my_corpus <- tm::tm_map(my_corpus, tm::removePunctuation)
my_corpus <- tm::tm_map(my_corpus, tm::content_transformer(tolower))
my_corpus <- tm::tm_map(my_corpus, removeWords, stopwords("english"))
my_corpus <- tm::tm_map(my_corpus, stemDocument)
# plot wordcloud
wordcloud::wordcloud(my_corpus, max.words=250, random.order=FALSE, color=rainbow(100))

Palabras como miss, sad, hate, can’t, suck, bad, bore, dont, work, now, day son algunas de las que más se mencionan en este sentimiento negativo.

Sentimiento Positivo:

En Text.

my_corpus <- tm::VCorpus(tm::VectorSource(df_train_positive$text))
# tidy text
my_corpus <- tm::tm_map(my_corpus, tm::removePunctuation)
my_corpus <- tm::tm_map(my_corpus, tm::content_transformer(tolower))
my_corpus <- tm::tm_map(my_corpus, removeWords, stopwords("english"))
my_corpus <- tm::tm_map(my_corpus, stemDocument)
# plot wordcloud
wordcloud::wordcloud(my_corpus, max.words=250, random.order=FALSE, color=rainbow(100))

En selected_text.

my_corpus <- tm::VCorpus(tm::VectorSource(df_train_positive$selected_text))
# tidy text
my_corpus <- tm::tm_map(my_corpus, tm::removePunctuation)
my_corpus <- tm::tm_map(my_corpus, tm::content_transformer(tolower))
my_corpus <- tm::tm_map(my_corpus, removeWords, stopwords("english"))
my_corpus <- tm::tm_map(my_corpus, stemDocument)
# plot wordcloud
wordcloud::wordcloud(my_corpus, max.words=250, random.order=FALSE, color=rainbow(100))

Palabras como good, love, happi, thank, day, great, hope, like, lol, mother, nice, fun, awesome son algunas de las destacadas para el sentimiento positivo.

Sentimiento Neutral:

En text. No lo vamos a realizar sobre selected_text puesto que no se diferencian mucho, como hemos dicho en varias ocasiones anteriormente.

my_corpus <- tm::VCorpus(tm::VectorSource(df_train_neutral$text))
# tidy text
my_corpus <- tm::tm_map(my_corpus, tm::removePunctuation)
my_corpus <- tm::tm_map(my_corpus, tm::content_transformer(tolower))
my_corpus <- tm::tm_map(my_corpus, removeWords, stopwords("english"))
my_corpus <- tm::tm_map(my_corpus, stemDocument)
# plot wordcloud
wordcloud::wordcloud(my_corpus, max.words=250, random.order=FALSE, color=rainbow(100))

Palabras como day, work, just, get, now, today, time son algunas de las que se encuentran en sentmiento neutral, lo cierto que podríamos decir que son palabras vacías sin contexto.

Terminamos ofreciendo la información de la sesión.

sessionInfo()
## R version 4.0.0 (2020-04-24)
## Platform: x86_64-w64-mingw32/x64 (64-bit)
## Running under: Windows 10 x64 (build 18362)
## 
## Matrix products: default
## 
## locale:
## [1] LC_COLLATE=Spanish_Spain.1252  LC_CTYPE=Spanish_Spain.1252   
## [3] LC_MONETARY=Spanish_Spain.1252 LC_NUMERIC=C                  
## [5] LC_TIME=Spanish_Spain.1252    
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
##  [1] gridExtra_2.3      wordcloud_2.6      RColorBrewer_1.1-2 sentimentr_2.7.1  
##  [5] stringdist_0.9.5.5 forcats_0.5.0      stringr_1.4.0      dplyr_1.0.0       
##  [9] purrr_0.3.4        readr_1.3.1        tidyr_1.1.0        tibble_3.0.1      
## [13] ggplot2_3.3.1      tidyverse_1.3.0    tm_0.7-7           NLP_0.2-0         
## [17] cowplot_1.0.0     
## 
## loaded via a namespace (and not attached):
##  [1] Rcpp_1.0.4.6       textshape_1.7.1    lubridate_1.7.8    lattice_0.20-41   
##  [5] assertthat_0.2.1   digest_0.6.25      slam_0.1-47        R6_2.4.1          
##  [9] cellranger_1.1.0   backports_1.1.7    reprex_0.3.0       evaluate_0.14     
## [13] httr_1.4.1         pillar_1.4.4       rlang_0.4.6        readxl_1.3.1      
## [17] rstudioapi_0.11    data.table_1.12.8  textclean_0.9.3    blob_1.2.1        
## [21] rmarkdown_2.2      labeling_0.3       munsell_0.5.0      broom_0.5.6       
## [25] compiler_4.0.0     modelr_0.1.8       xfun_0.14          pkgconfig_2.0.3   
## [29] qdapRegex_0.7.2    htmltools_0.4.0    tidyselect_1.1.0   fansi_0.4.1       
## [33] crayon_1.3.4       dbplyr_1.4.4       withr_2.2.0        SnowballC_0.7.0   
## [37] grid_4.0.0         nlme_3.1-147       jsonlite_1.6.1     gtable_0.3.0      
## [41] lifecycle_0.2.0    DBI_1.1.0          magrittr_1.5       scales_1.1.1      
## [45] KernSmooth_2.23-16 cli_2.0.2          stringi_1.4.6      farver_2.0.3      
## [49] fs_1.4.1           syuzhet_1.0.4      xml2_1.3.2         ellipsis_0.3.1    
## [53] generics_0.0.2     vctrs_0.3.0        tools_4.0.0        glue_1.4.1        
## [57] hms_0.5.3          parallel_4.0.0     yaml_2.2.1         colorspace_1.4-1  
## [61] lexicon_1.2.1      rvest_0.3.5        knitr_1.28         haven_2.3.0

Referencias y Comentarios

Referencias

Parte de este código ha sido recopilado de las lecciones del profesor Santiago Mota, Master in Big Data and & Data Science at Complutense University of Madrid. También, otra parte del mismo ha sido inspirado a través de código diverso de los notebooks de Kaggle puestos a disposición por parte de los usuarios inscritos, aquí.

Comentarios

El presente código ha sido ejecutado en el sistema operativo version Windows 10, 64 bits, escogiendo formato de codificación en RStudio UTF-8.

 

A work by María Luisa Duque

marialdu@umc.es